\section{DOM}

文档对象模型DOM（Document Object Module）是由W3C定义的提供与任何HTML或XML文档进行交互的API（编程接口），可以说是HTML之后的又一伟大创新。它使得用户可以通过javaScript访问HTML文档中的任意元素和内容，DOM提供的接口可以操作HTML文件中的节点。当浏览器加载一个网页后，这个网页就可以看作是文档树，由多个节点构成。所谓DOM，就是将HTML文档中各个元素按照从属关系建立起的模型。总的来说，我们可以利用DOM完成以下应用：

\begin{itemize}
	\item 访问指定节点；
	\item 访问相关节点；
	\item 访问节点属性；
	\item 检查节点类型；
	\item 创建节点；
	\item 为节点添加事件；
	\item 操作节点。
\end{itemize}

\subsection{HTML文档与DOM}

我们先看下面这个简单的HTML文档：

\begin{lstlisting}
<!DOCTYPE html>
<html lang="zh">
<head>
	<meta charset="utf-8">
	<title>一个简单的HTML文档</title>
</head>
<body>
	<h1>这是一个简单的HTML文档</h1>
	<p>文档中包含着段落和<a title="link" href="#">超级链接</a></p>
</body>
</html>
\end{lstlisting}

这个HTML文档的DOM可示例如下图\ref{fig:DOM}：

\begin{figure}[htbp]
	\centering
	\includegraphics[width=120mm]{raw/DOM.pdf}
	\caption{DOM模型}
	\label{fig:DOM}
\end{figure}

在上面的DOM中，html位于最顶端，是DOM的跟节点，head和body是html的子节点，它们属于同一层，并不互相包含，是兄弟关系，h1和p是兄弟元素，其父元素是body，p的子元素是a元素。

\subsection{节点}

节点（node）的概念来源于计算机网络，它代表网络中的一个连接点。在DOM中，文档也是由节点构成的集合。DOM定义了多种节点，常用的是元素节点、文本节点、和属性节点，分别对应元素、元素中包含的文字内容和元素的属性。

例如上面的代码中：

\begin{lstlisting}
<a title="link" href="#">超级链接</a>
\end{lstlisting}

a元素的title和href属性就是a元素节点的属性节点，a元素所包含的内容“超级链接”就是文本节点。如下图\ref{fig:node}所示：

\begin{figure}[htbp]
	\centering
	\includegraphics[width=120mm]{raw/node.pdf}
	\caption{各种节点及关系}
	\label{fig:node}
\end{figure}

\subsection{使用DOM}

DOM提供了一种访问文档内容的机制，我们可以使用DOM处理HTML文档的内容。DOM由节点（node）构成，每一个节点，都有一系列的属性、方法可以使用，常用属性、方法见下表\ref{tab:node常用属性和方法}：

% Table generated by Excel2LaTeX from sheet 'Sheet1'
\begin{table}[htbp]
  \centering
  \caption{node常用属性和方法}
    \begin{tabularx}{\linewidth}{X|X|X}
    \toprule
    {属性/方法} & {类型/返回类型} & {说明} \\
    \midrule
    nodeName & String  & 节点名称 \\
    \hline
    nodeValue & String  & 节点的值 \\
    \hline
    nodeType & Number  & 节点类型 \\
    \hline
    firstChild & Node    & childNodes列表的第一个节点 \\
    \hline
    lastChild & Node    & childNodes列表的最后一个节点 \\
    \hline
    childNodes & NodeList & 所有子节点列表，方法item(i)可以访问其中节点 \\
    \hline
    parentNode & Node    & 父节点 \\
    \hline
    previousSibling & Node    & 指向前一个兄弟节点 \\
    \hline
    nextSibling & Node    & 指向后一个兄弟节点 \\
    \hline
    hasChildNodes() & Boolean & 是否包含子节点 \\
    \hline
    attributes & NameNodeMap & 包含一个元素特性的Attr对象 \\
    \hline
    appendChild(node) & Node    & 将node节点添加到childNodes末尾 \\
    \hline
    removeChild(node) & Node    & 从childNOdes中删除node节点 \\
    \hline
    repaceChild(new,old) & Node    & 将childNodes中的oldnode节点替换为newnode节点 \\
    \hline
    insertBefore(new,ref) & Node    & 在childNOdes中的refnode节点之前插入newnode节点 \\
    \hline
    innerHTML & String  & 读取或者设置某个标记之间的所有内容 \\
    \hline
    className & String  & 读取或者设置节点的CSS类别 \\
    \bottomrule
    \end{tabularx}%
  \label{tab:node常用属性和方法}%
\end{table}%


\subsubsection{访问节点}
DOM提供了一些很便捷的方法来访问文档的节点，最常用的是getElementsByTagName()和getElementById()，此外比较常用的是getElementsByName()、getElementsByClassName();

\begin{lstlisting}
……
		var oLi = document.getElementsByTagName('li');
		for(var i = 0 in oLi) {
        console.log(i + '=' + oLi[i]);
    }
		console.log(oLi);
		console.log(oLi.length);
		console.log(oLi[0].childNodes[0].nodeValue);
……
\end{lstlisting}

将访问oLi子节点列表中的第一个节点的值。

\subsubsection{检测节点类型}

通过节点的nodeType属性可以检测出节点的类型，DOM定义了12种类型，大多数情况下，我们用到的是以下类型：

\begin{enumerate}
	\item ELEMENT\_NODE 元素节点 nodeType的值为1；
	\item ATTRIBUTE\_NODE 属性节点 nodeType的值为2；
	\item TEXT\_NODE 文本节点 nodeType的值为3；
\end{enumerate}

\subsubsection{利用父子兄关系查找节点}
在获取了某个节点之后，可以通过父子关系，利用hasChildNodes()方法和childNodes属性获取该节点所包含的所有子节点。例如：

\begin{lstlisting}
……
		var oLi = document.getElementById('myList');
		var DOMString = "";
		if(oLi.hasChildNodes()){
			var oChild = oLi.childNodes;
			for(var i=0; i<oChild.length;i++){
				DOMString += oChild[i].nodeName + "\n";
			}
		}
		console.log(DOMString);
……
\end{lstlisting}

从运行结果我们可以看出，不光有元素节点，连它们之间的空格也被当成了子节点。

通过parentNode属性，可以获取父元素节点。如：

\begin{lstlisting}
……
		var myItem = document.getElementById('active');
		console.log(myItem.parentNode.tagName);
……
\end{lstlisting}

DOM还提供了处理兄弟之间关系的属性和方法，如nextSibling、previousSibling。

\subsubsection{设置节点属性}
找到节点后，可以通过getAttribute()、setAttitude()方法取得或者设定节点的属性。

\begin{lstlisting}
……
var myItem = document.getElementById('active');
console.log(myItem.parentNode.tagName);
console.log(myItem.getAttribute("title"));
myItem.setAttribute("title","主要用以和用户交互");
console.log(myItem.getAttribute("title"));
……
\end{lstlisting}

\subsubsection{创建和添加节点}
创建元素节点采用creatElment(),创建文本节点采用creatTextNode(),创建文档碎片节点采用creatDocumentFragment()等等。

在插入元素之前，先将元素内容添加到元素节点，再将元素节点及其包含的文本节点添加到指定节点。

\begin{lstlisting}
……
var oP = document.createElement("p");
var oText = document.createTextNode("这是一个使用DOM生成的段落");
oP.appendChild(oText);
document.body.appendChild(oP);
……
\end{lstlisting}

\subsubsection{删除节点}
删除节点使用removeChild()方法，通常先找到要删除的节点，然后利用parentNode属性找到父节点，然后使用父节点的removeChild方法。

\begin{lstlisting}
……
<body onload="removeLi()">
	<ul id="myList">
		<li>HTML</li>
		<li>CSS</li>
		<li id="active" title="JavaScript是脚本编程语言">JavaScript</li>
	</ul>
	<script>
	function removeLi() {
		var oLi = document.getElementById('active');
        oLi.parentNode.removeChild(oLi);
	}
	</script>
</body>
……
\end{lstlisting}

\subsubsection{替换节点}
DOM提供replaceChild()方法来替换节点，具体过程和删除节点类似，先找到想要替换的节点，再创建新节点，然后使用父节点替换方法。

\begin{lstlisting}
……
<body onload="replaceLi()">
	<ul id="myList">
		<li>HTML</li>
		<li>CSS</li>
		<li id="active">JavaScript</li>
	</ul>
	<script>
	function replaceLi() {
		var oOldLi = document.getElementById('active');
        var oNewLi = document.createElement('li');
        var oText = document.createTextNode('ECMAScript');
        oNewLi.appendChild(oText);
        oOldLi.parentNode.replaceChild(oNewLi,oOldLi);
	}
	</script>
</body>
……
\end{lstlisting}

\subsubsection{innerHTML属性}
innerHTML属性虽然不是W3C DOM的组成部分，但它得到了目前主流浏览器的支持。该属性表示某个标记之间的所有内容，包括代码本身。

\begin{lstlisting}
var oLi = document.getElementById('active');
console.log(oLi.innerHTML);
oLi.innerHTML = "<a href='http://www.baidu.com'>Baidu</a>";
\end{lstlisting}

\subsubsection{className属性}
className属性是一个非常实用的属性，可以修改一个节点的CSS类别。

\begin{lstlisting}
var oLi = document.getElementById('active');
console.log(oLi.className);
oLi.className += ' jsdemo';
console.log(oLi.className);
\end{lstlisting}

使用className属性如果要追加CSS样式，而不是覆盖CSS样式的话，使用“+=”连接运算符，另外注意不同的样式名称使用空格分割。
